﻿// Copyright (c) 2024 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.


#pragma once

#include <cstddef>
#include <cstdint>
#include <memory>

#include "ioxx_data_basic.hpp"
#include "lipid_count.hpp"

// 各种单输入 / 单输出的数据处理环节
namespace lipid {

    using DefaultFilterTimeType = uint32_t;
    constexpr size_t DefaultFilterBufferSize = 10;

    /**
     * @brief 滤波器接口
     *
     * @tparam V  输入数据类型
     * @tparam R  返回数据类型
     */
    template <typename V, typename R>
    class IFilter {
       public:
        /**
         * @brief 向滤波器输入一个值，获得输出
         *
         * @param val        待滤波的一个原始数据点
         * @return R
         */
        virtual R feed(V val) = 0;

        /**
         *@param timestamp 时间戳
         *
         */
        // virtual R feed(V val, T timestamp) = 0;
        // TODO: 某些滤波器可能需要输入的原始数据具有时间信息，此时要使用带有timestamp 参数的重载。

        /**
         * @brief 重置有记忆的滤波器的输出为指定的值
         *
         * 滑动滤波等有时间窗口的滤波器都会在缓冲区中记忆一段数据，
         * 调用reset 可以重置缓冲区，并可能将下一次输出设置为指定值。
         *
         * 对信号钳位等无记忆的滤波器无效。
         *
         * @param init_val  重置后的初始输出值
         */
        virtual void reset(R init_val) = 0;

        virtual ~IFilter(){};
    };


    template <typename V, typename R, size_t N = 0>
    class MovingAverageFilter : public IFilter<V, R> {
       public:
        using BufferType = data_basic::RingBuffer<V, N>;
        using WindowSizeType = typename BufferType::IndexType;

       private:
        BufferType _buf;
        V _max_increment;  // 新加入的数据点相比前一个数据点的增量的最大值，
        // 如果超限，新数据视为无效，用前一数据点占位. 值为0 表示不设限.
        V _sum;
        V _init_avg;
        WindowSizeType _window_size;


       public:
        explicit MovingAverageFilter(R init_avg = 0, WindowSizeType window_size = N, V max_increment = 0) :
            _max_increment(max_increment), _window_size(window_size) {
            this->reset(init_avg);
        }

        virtual R feed(V val) override {
            using namespace std;
            if (_max_increment > 0) {
                V pre = _buf.back();
                V delta = abs(val - pre);

                if (delta > _max_increment) {  // 新数据超限，丢弃新数据，把前一个数据作为代替加入缓冲区
                    val = pre;
                }
            }

            _sum -= _buf.pop_front();
            _sum += val;
            _buf.push_back(val);
            return static_cast<R>(_sum / static_cast<V>(_window_size));
        }

        virtual void reset(R init_val) override {
            _sum = static_cast<V>(init_val) * static_cast<V>(_window_size);
            _buf.clear();
            _buf.init(init_val, _window_size);
        }

        void set_window_size(WindowSizeType size) {
            _window_size = size;
        }

        void set_max_increment(V val) {
            _max_increment = val;
        }

        R get_average() const {
            return static_cast<R>(_sum / static_cast<V>(_window_size));
        }

        auto get_window_size() const {
            return _window_size;
        }

        virtual ~MovingAverageFilter() {}
    };


    template <typename V, typename R>
    class GainThenLimit : IFilter<V, R> {
       private:
        V _gain = 1;
        R _limit_min = 0.0f;
        R _limit_max = 0.0f;

       public:
        GainThenLimit(V gain = 1, R limit_min = 0, R limit_max = 0) :
            _gain(gain), _limit_min(limit_min), _limit_max(limit_max) {}


        virtual R feed(V val) override {
            if (_gain != 1) {
                val *= _gain;
            }

            return value_constrain(static_cast<R>(val), _limit_min, _limit_max);
        }

        virtual void reset(R init_val) override {}

        virtual ~GainThenLimit() {}
    };


    template <typename V, typename R, size_t N>
    class CompoundFilter : public IFilter<V, R> {
       public:
        using SizeType = data_basic::enough_index_type_t<N>;
        using FilterType = IFilter<V, V>;

       private:
        FilterType* _ftr_ptr_list[N];

       public:
        template <SizeType Which>
        void set_filter(FilterType* ftr_ptr) {
            static_assert(Which < N);
            _ftr_ptr_list[Which] = ftr_ptr;
        }

        virtual R feed(V val) override {
            for (auto* ftr_ptr : _ftr_ptr_list) {
                if (ftr_ptr != nullptr) {
                    val = ftr_ptr->feed(val);
                }
            }

            return static_cast<R>(val);
        }

        virtual void reset(R init_val) override {
            for (auto* ftr_ptr : _ftr_ptr_list) {
                if (ftr_ptr != nullptr) {
                    ftr_ptr->reset(init_val);
                }
            }
        }
    };


}  // namespace lipid::filter